package no.ntnu.fp.model;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Date;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import nu.xom.Attribute;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Node;

public class SuperPackage {
	public class WTFException extends RuntimeException {

	}

	public static final String MODEL_PACKAGE = "no.ntnu.fp.model";
	private static final Object COMMAND_CLASS_NAME = "Command";

	public Document toDocument() {
		Element root = new Element("superpakke");
		for (String className : classMap.keySet()) {
			Map<Integer, Map<String, Object>> modelMap = getModelMap(className);
			for (Integer id : modelMap.keySet()) {
				Element eClassName = new Element(className);
				eClassName.addAttribute(new Attribute("id", "" + id));
				root.appendChild(eClassName);
				Map<String, Object> attributeMap = getAttributeMap(modelMap, id);
				for (String attribute : attributeMap.keySet()) {
					Element attributeElement = new Element(attribute);
					attributeElement.appendChild(""
							+ attributeMap.get(attribute));
					eClassName.appendChild(attributeElement);
				}
			}
		}

		return new Document(root);
	}

	/**
	 * Lager
	 * 
	 * @param doc Dokumentet den trenger
	 */
	public SuperPackage(Document doc) {
		Element root = (Element) doc.getChild(0);
		for (int i = 0; i < root.getChildCount(); i++) {
			Element aModel = (Element) root.getChild(i);
			int aModelId = Integer.parseInt(aModel.getAttribute("id")
					.getValue());
			Map<Integer, Map<String, Object>> aModelMap = getModelMap(aModel
					.getLocalName());
			Map<String, Object> attributeMap = getAttributeMap(aModelMap,
					aModelId);
			aModelMap.put(aModelId, attributeMap);

			for (int j = 0; j < aModel.getChildCount(); j++) {
				Node attributeNode = aModel.getChild(j);
				if (attributeNode instanceof Element) {
					Element attributeElement = (Element) attributeNode;
					String attribute = attributeElement.getLocalName();
					String value = attributeElement.getValue();
					attributeMap.put(attribute, value);
				}
			}
		}
	}

	public SuperPackage() {
	}

	public void pp() {
		for (String className : classMap.keySet()) {
			System.out.println("<" + className + ">");
			Map<Integer, Map<String, Object>> modelMap = getModelMap(className);
			for (Integer id : modelMap.keySet()) {
				System.out.print("\t[" + id + "]: ");
				Map<String, Object> attributeMap = getAttributeMap(modelMap, id);
				for (String attribute : attributeMap.keySet()) {
					System.out.print(attribute + "='"
							+ attributeMap.get(attribute) + "' ");
				}
				System.out.println();
			}
			System.out.println("<\\" + className + ">\n");
		}
	};

	/**
	 * Pakkestruktur:
	 * 
	 * ClassName -> Instance (ID) -> Attribute -> Value
	 * 
	 * Person -> 2 -> name -> "Bjarne" Person -> 2 -> email ->
	 * "bjarnefest@gmail.com" Room -> 4 -> capacity -> 15
	 * 
	 * String -> Integer -> String -> Object classMap -> modelMap ->
	 * attributeMap
	 */

	// Class IDs Attr Value Class IDs Attr Value
	Map<String, Map<Integer, Map<String, Object>>> classMap = new HashMap<String, Map<Integer, Map<String, Object>>>();

	public void put(Packable model) {
		String className = model.getClass().getSimpleName();
		Map<Integer, Map<String, Object>> modelMap = getModelMap(className);
		Map<String, Object> attributeMap = getAttributeMap(modelMap,
				model.getId());

		Map<String, Object> modelsMapTemp = model.pack();
		for (String attribute : modelsMapTemp.keySet()) {
			attributeMap.put(attribute, modelsMapTemp.get(attribute));
		}
	}

	public List<AbstractModel> run() throws ClassNotFoundException, SecurityException,
			NoSuchMethodException, IllegalArgumentException,
			IllegalAccessException, InvocationTargetException {
		List<AbstractModel> list = new ArrayList<AbstractModel>();
		
		for (String className : classMap.keySet()) {
			Map<Integer, Map<String, Object>> modelMap = getModelMap(className);

			if (isCommandClass(className))
				continue;
			
			Class klass = Class.forName(MODEL_PACKAGE + "." + className);
			Method getInstance = klass.getMethod("get", int.class);
			
			for (Integer id : modelMap.keySet()) {
				AbstractModel o = (AbstractModel) getInstance.invoke(klass, id);
				Map<String, Object> map = modelMap.get(id);
				if(map==null) throw new WTFException();
				list.add(o);
				o.unpack(map);
			}
		}
		
		return list;
	}
	
	public void runAsServer() throws ClassNotFoundException, SecurityException,
			NoSuchMethodException, IllegalArgumentException,
			IllegalAccessException, InvocationTargetException {
		for (String className : classMap.keySet()) {
			Map<Integer, Map<String, Object>> modelMap = getModelMap(className);

			Class klass = Class.forName(MODEL_PACKAGE + "." + className);
			if (isCommandClass(className)) {
				Method newCommand = klass.getMethod("newCommand");
				
				for (Integer id : modelMap.keySet()) {
					Command command = (Command) newCommand.invoke(null);
					command.unpack(modelMap.get(id));
					command.run();
				}
			} else {
				Method getInstance = klass.getMethod("getInstance", int.class);

				for (Integer id : modelMap.keySet()) {
					Packable o = (Packable) getInstance.invoke(klass,
							id);
					o.unpack(modelMap.get(id));
				}
			}
		}
	}

	private boolean isCommandClass(String className) {
		return className.equals(COMMAND_CLASS_NAME);
	}

	private Map<String, Object> getAttributeMap(
			Map<Integer, Map<String, Object>> modelMap, int id) {
		if (modelMap.containsKey(id)) {
			return modelMap.get(id);
		} else {
			Map<String, Object> attributeMap = new HashMap<String, Object>();
			modelMap.put(id, attributeMap);
			return attributeMap;
		}
	}

	private Map<Integer, Map<String, Object>> getModelMap(String className) {
		if (classMap.containsKey(className)) {
			return classMap.get(className);
		} else {
			Map<Integer, Map<String, Object>> modelMap = new HashMap<Integer, Map<String, Object>>();
			classMap.put(className, modelMap);
			return modelMap;
		}
	}
}
